<?php
/**
 * Created by PhpStorm.
 * User: Slagga
 * Date: 2017/4/23
 * Time: 17:21
 */

// HTTP  范围 还可以在这里增加你的认证（可选）。
// 文件
$file = __DIR__.'/numbers.txt';
$content_type = 'text/plain';

// 检查是否可读，并得到文件大小
if (($filelength = filesize($file)) === false) {
    error_log("Problem reading filesize of $file.");
}

// 解析首部来确定发送响应所需的信息
if (isset($_SERVER['HTTP_RANGE'])) {
    // 定界符不区分大小写
    if (!preg_match('/bytes=\d*-\d*(,\d*-\d*)*$/i', $_SERVER['HTTP_RANGE'])) {
        error_log("Client requested invalid Range.");
        send_error($filelength);
        exit();
    }

    /**
     * 规范：客户在一个请求中请求多个字节范围（byte-ranges）时，服务器应当按它们在请求中出现的顺序返回这些范围
     */
    $ranges = explode(',', substr($_SERVER['HTTP_RANGE'], 6));// 字节=后面的内容
    $offsets = [];
    // 抽取和验证每个部分    只保存通过验证的部分
    foreach ($ranges as $range) {
        $offset = parse_offset($range, $filelength);
        if ($offset !== false) {
            $offsets[] = $offset;
        }
    }

    /**
     * 取决于所请求的合法范围的个数   必须采用不同的格式返回响应
     */
    switch (count($offsets)) {
        case 0:
            // 非合法范围
            error_log("Client requested no valid ranges.");
            sent_error($filelength);
            exit;
            break;
        case 1:
            // 一个合法范围，发送标准应答
            http_response_code(206);//部分类容
            list($start, $end) = $offsets[0];
            header("Content-Range: bytes $start-$end/$filelength");
            header("Content-Type: $content_type");

            //设置变量，从而可以在这里以及下一个情况中重用代码
            // 注意： 0-0 为 1 字节， 因为范围包含其两个端点
            $content_length = $end - $start + 1;
            $boundaries = [0 => '', 1 => ''];
            break;
        default:
            // 多个合法范围，发送多部分应答
            http_response_code(206);
            $boundary = str_rand(32);

            /**
             * 需要计算整个相应的内容长度（Content-Length），
             * 不过将整个响应加载到一个字符串中会占用大量内存
             * 所以使用偏移量计算值。  另外利用这个机会计算边界。
             */
            $boundaries = [];
            $content_length = 0;

            foreach ($offsets as $offset) {
                list($start, $end) = $offset;
                // 用于分解各个部分
                $boundary_header = "\r\n"."--$boundary\r\n"."Content-Type: $content_type\r\n"."Content-Range: bytes $start-$end/$filelength\r\n"."\r\n";
                $content_length += strlen($boundary_header) + ($end - $start + 1);
                $boundaries[] = $boundary_header;
            }

            // 增加结束边界
            $boundary_header = "\r\n--$boundary--";
            $content_length += strlen($boundary_header);
            $boundaries[] = $boundary_header;

            // 去除第一个边界中多余的 \r\n
            $boundaries[0] = substr($boundaries[0], 2);
            $content_length -= 2;

            // 改为特殊的多部分内容类型（Content-Type）
            $content_type = "multipart/byteranges; boundary=$boundary";
    }
} else {
    // 发送整个文件   设置变量，就好像这是从 Range 首部抽取的
    $start = 0;
    $end = $filelength - 1;
    $offset = [$start, $end];
    $offsets = [$offset];

    $content_length = $filelength;
    $boundaries = [0 => '', 1 => ''];
}

// 指出得到的是什么
header("Content-Type: $content_type");
header("Content-Length: $content_length");
// 提供给用户
$handle = fopen($file, 'r');
if ($handle) {
    $offsets_count = count($offsets);
    // 输出各个定界符和文件的适当部分
    for ($i = 0; $i < $offsets_count; $i++) {
        print $boundaries[$i];
        list($start, $end) = $offsets[$i];
        send_range($handle, $start, $end);
    }
    //结束边界
    print $boundaries[$i];
    fclose($handle);
}

// 在文件中移动适当的位置  按块输出所请求的部分
function send_range($handle, $start, $end) {
    $line_length = 4096;

    if (fseek($handle, $start) === -1) {
        error_log("Error: fseek() fail.");
    }

    $left_to_read = $end - $start + 1;
    do {
        $length = min($line_length, $left_to_read);
        if (($buffer = fread($handle, $length)) !== false) {
            print $buffer;
        } else {
            error_log("Error: fread() fail.");
        }
    } while ($left_to_read -= $length);
}

// 发送失败头部
function send_error($filelength) {
    http_response_code(416);
    header("Content-Range: bytes */$filelength");//响应码 416 要求发送这个首部
}

// 将一个偏移量转换为文件中的开始和结束位置     或者，如果偏移量非法的，则返回false
function parse_offset($range, $filelength) {
    list($start, $end) = explode('-', $range);

    if ($start === '') {
        if ($end === '' || $end === 0){
            return false;
        } else {
            $start = max(0, $filelength - $end);
            $end = $filelength - 1;
        }
    } else {
        if ($end == '' || $end > $filelength - 1) {
            $end = $filelength - 1;
        }

        if($start > $end) {
            return false;
        }
    }
    return [$start, $end];
}

// 生成一个随机字符串来分割响应中的各个部分
function str_rand($length = 32, $characters='0123456789abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ') {
    if (!is_int($length) || $length < 0) {
        return false;
    }

    $characters_length = strlen($characters) - 1;
    $string = '';

    for ($i = $length; $i > 0; $i--) {
        $string .= $characters[mt_rand(0, $characters_length)];
    }
    return $string;
}





